package cc.telecomdigital.tdstock.activity.groups.more.push.client;

/*
 * Copyright (C) 2010 Moduad Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Future;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Registration;
import org.jivesoftware.smack.provider.ProviderManager;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Handler;
import cc.telecomdigital.tdstock.SystemInfo;
import cc.telecomdigital.tdstock.Framework.UniqueItem.TDStockLog;
import cc.telecomdigital.tdstock.activity.groups.more.push.util.Data;
import cc.telecomdigital.tdstock.activity.groups.more.push.util.NotificationProfile;

/**
 * This class is to manage the XMPP connection between client and server.
 * 
 * @author hunter
 */
public class XmppManager
{

	private static final String				TAG				= XmppManager.class
																.getSimpleName();

	private static final String				XMPP_RESOURCE_NAME	= "TDStock_%s_%s";
	// private static final String XMPP_RESOURCE_NAME = "AndroidpnClient";

	private Context						context;

	private NotificationService.TaskSubmitter	taskSubmitter;

	private NotificationService.TaskTracker		taskTracker;

	private SharedPreferences				sharedPrefs;

	private String							xmppHost;

	private int							xmppPort;

	private XMPPConnection					connection;

	private String							username;

	private String							password;

	private ConnectionListener				connectionListener;

	private PacketListener					notificationPacketListener;

	private Handler						handler;

	private List<Runnable>					taskList;

	private boolean						running			= false;

	private Future<?>						futureTask;

	public static int						waiting			= 0;

	private String							newUsername;

	private String							newPassword;

	//private Timer timer;
	//private Thread reconnection;
	// -2----正在断开，-1-----连接断开，0--默认初始，1---已经连接，2---正在连接
	/*	public ConnectionStatus status = ConnectionStatus.DEFAULT;

		public enum ConnectionStatus {
			DISCONNING(-2), DISCONN(-1), DEFAULT(0), CONN(1), CONNING(2);
			private int value;

			ConnectionStatus(int num) {
				this.value = num;
			}

			public int getConnectionStatus() {
				return value;
			}
		}*/

	/*	public ConnectionStatus getStatus() {
			return status;
		}

		public void setStatus(ConnectionStatus status) {
			this.status = status;
		}
	*/
	/*	public Thread getReconnection() {
			return reconnection;
		}

		public void setReconnection(Thread reconnection) {
			this.reconnection = reconnection;
		}*/

	public XmppManager(NotificationService notificationService)
	{
		context = notificationService;
		taskSubmitter = notificationService.getTaskSubmitter();
		taskTracker = notificationService.getTaskTracker();
		sharedPrefs = notificationService.getSharedPreferences();
		Data.getDomainAndAccountAndTokenAndDevice_token(notificationService);
		xmppHost = sharedPrefs.getString(Constants.XMPP_HOST,
				Constants.XMPP_DEFAULT_HOST);
		xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, Integer
				.parseInt(Constants.XMPP_DEFAULT_PORT));
		username = sharedPrefs.getString(Constants.XMPP_USERNAME, "");
		password = sharedPrefs.getString(Constants.XMPP_PASSWORD, "");

		connectionListener = new PersistentConnectionListener(this);
		notificationPacketListener = new NotificationPacketListener(this);

		handler = new Handler();
		taskList = new ArrayList<Runnable>();
		//reconnection = new ReconnectionThread(this);
		newUsername = SystemInfo.getDeviceId(context);
		newPassword = newRandomUUID();
	}

	public String getXmppHost()
	{
		return xmppHost;
	}

	public void setXmppHost(String xmppHost)
	{
		this.xmppHost = xmppHost;
	}

	public Context getContext()
	{
		return context;
	}

	public void connect()
	{
		/*		if (XmppManager.this.isConnected()) {
					TDStockLog.d(TAG, "disconnect....run()");
					XmppManager.this.getConnection().disconnect();
				}*/
		TDStockLog.d(TAG, "Xmpper----------->connect()...");
		submitLoginTask();
	}

	public void disconnect()
	{
		TDStockLog.d(TAG, "disconnect()...");
		waiting = 0;
		//setStatus(ConnectionStatus.DISCONN);
		terminatePersistentConnection();
	}

	public void terminatePersistentConnection()
	{
		Runnable runnable = new Runnable()
		{

			final XmppManager	xmppManager	= XmppManager.this;

			public void run()
			{
				if (xmppManager.isConnected())
				{
					TDStockLog.d(TAG,
							"terminatePersistentConnection()... run()");
					xmppManager.getConnection().removePacketListener(
							xmppManager.getNotificationPacketListener());
					xmppManager.getConnection().disconnect();
				}
				xmppManager.runTask();
			}

		};
		addTask(runnable);
	}

	public synchronized XMPPConnection getConnection()
	{
		return connection;
	}

	public void setConnection(XMPPConnection connection)
	{
		this.connection = connection;
	}

	public String getUsername()
	{
		return username;
	}

	public void setUsername(String username)
	{
		this.username = username;
	}

	public String getPassword()
	{
		return password;
	}

	public void setPassword(String password)
	{
		this.password = password;
	}

	public ConnectionListener getConnectionListener()
	{
		return connectionListener;
	}

	public PacketListener getNotificationPacketListener()
	{
		return notificationPacketListener;
	}

	public void startReconnectionThread(final String from)
	{
		/*		synchronized (reconnection) {
					if (!reconnection.isAlive()) {
						reconnection.setName("Xmpp Reconnection Thread");
						reconnection.start();
					}
				}
				addTask(new ReconnectionThread(this));
				this.runTask();
				
				timer.schedule(new TimerTask() {
					
					@Override
					public void run() {
						// TODO Auto-generated method stub
						connect();
					    runTask();
					    waiting++;
					}
				}, System.currentTimeMillis(), 100);*/
		if (handler == null)
		{
			handler = new Handler();
		}
		handler.postDelayed(new Runnable()
		{

			@Override
			public void run()
			{
				// TODO Auto-generated method stub
				TDStockLog.d(TAG, "startReconnectionThread  from to "
						+ from);
				//taskList.clear();
				running = false;
				addTask(new ConnectTask());
				addTask(new RegisterTask());
				addTask(new LoginTask());
				//connect();
				//add()
				//new Thread(new ConnectTask()).start(); 
				//runTask();
				waiting++;
				if (waiting == 1)
				{
				}
				else
				{
					TDStockLog.d(TAG, "This is the  " + waiting
							+ "  reconnection!  Trying to reconnect in "
							+ waiting() + " seconds");
				}
			}
		}, waiting() * 1000);
	}

	public Handler getHandler()
	{
		return handler;
	}

	public void reregisterAccount()
	{
		removeAccount();
		submitLoginTask();
		runTask();
	}

	public List<Runnable> getTaskList()
	{
		return taskList;
	}

	public Future<?> getFutureTask()
	{
		return futureTask;
	}

	public void runTask()
	{
		TDStockLog.d(TAG, "runTask()...");
		synchronized (taskList)
		{
			running = false;
			futureTask = null;
			if (!taskList.isEmpty())
			{
				TDStockLog.d(TAG, "runnable count:" + taskList.size());
				Runnable runnable = (Runnable) taskList.get(0);
				taskList.remove(0);
				running = true;
				futureTask = taskSubmitter.submit(runnable);
				TDStockLog.d(TAG, "runTask()..."
						+ runnable.getClass().getSimpleName());
				if (futureTask == null)
				{
					taskTracker.decrease();
				}
			}
		}
		taskTracker.decrease();
		TDStockLog.d(TAG, "runTask()...done");
	}

	/*
	 * private String newRandomUUID() { String uuidRaw =
	 * UUID.randomUUID().toString(); Log.d("test", uuidRaw); return
	 * uuidRaw.replaceAll("-", ""); }
	 */
	private String newRandomUUID()
	{
		String uuidUserName = SystemInfo.getDeviceId(context);
		String uuidPassWord = "";
		char[] arr = uuidUserName.toCharArray();
		for (int i = arr.length - 1; i >= 0; i--)
		{
			uuidPassWord = uuidPassWord + arr[i];
		}
		uuidPassWord = UUID.nameUUIDFromBytes(uuidPassWord.getBytes())
				.toString();
		return uuidPassWord.replaceAll("-", "");
	}

	public boolean isConnected()
	{
		return connection != null && connection.isConnected();
	}

	private boolean isAuthenticated()
	{
		return connection != null && connection.isConnected()
				&& connection.isAuthenticated();
	}

	private boolean isRegistered()
	{
		return sharedPrefs.contains(Constants.XMPP_USERNAME)
				&& sharedPrefs.contains(Constants.XMPP_PASSWORD);
	}

	private void submitConnectTask()
	{
		TDStockLog.d(TAG, "submitConnectTask()...");
		addTask(new ConnectTask());
		// new Thread(new ConnectTask()).start();
	}

	private void submitRegisterTask()
	{
		TDStockLog.d(TAG, "submitRegisterTask()...");
		submitConnectTask();
		addTask(new RegisterTask());
		// new Thread(new RegisterTask()).start();
	}

	private void submitLoginTask()
	{
		TDStockLog.d(TAG, "submitLoginTask()...");
		submitRegisterTask();
		addTask(new LoginTask());
		// new Thread(new LoginTask()).start();
	}

	private void addTask(Runnable runnable)
	{
		TDStockLog.d(TAG, "addTask(runnable)...");
		taskTracker.increase();
		synchronized (taskList)
		{
			TDStockLog.d(TAG, (taskList.isEmpty() && !running) + "");
			if (taskList.isEmpty() && !running)
			{
				running = true;
				futureTask = taskSubmitter.submit(runnable);
				if (futureTask == null)
				{
					taskTracker.decrease();
				}
			}
			else
			{
				taskList.add(runnable);
				TDStockLog.d(TAG, "addTask(runnable)... "
						+ runnable.getClass().getSimpleName());
			}
			TDStockLog.d(TAG, "taskList runnable count:" + taskList.size());
		}
		TDStockLog.d(TAG, "addTask(runnable)... done");
	}

	private void removeAccount()
	{
		Editor editor = sharedPrefs.edit();
		editor.remove(Constants.XMPP_USERNAME);
		editor.remove(Constants.XMPP_PASSWORD);
		editor.commit();
	}

	/**
	 * A runnable task to connect the server.
	 */
	private class ConnectTask implements Runnable
	{

		final XmppManager	xmppManager;

		private ConnectTask()
		{
			this.xmppManager = XmppManager.this;
		}

		public void run()
		{
			TDStockLog.i(TAG, "ConnectTask.run()...");
			if (!xmppManager.isConnected())
			{
				// Create the configuration for this new connection
				ConnectionConfiguration connConfig = null;
				String[] objs = Data.verify(TAG, newUsername,context);
				//objs=null;
				//使用网络配置文件
				if (objs != null && objs.length == 2)
				{
					String host = objs[0];
					int port = Integer.parseInt(objs[1]);
					TDStockLog.d(TAG, String.format("host=%s,port=%d",
							host, port));

					//modified the default setting if got it from networking
					xmppHost = objs[0];
					xmppPort = Integer.parseInt(objs[1]);

					connConfig = new ConnectionConfiguration(objs[0],
							Integer.parseInt(objs[1]));

				}
				//使用本地配置文件
				else
				{
					TDStockLog.d(TAG, String.format("host=%s,port=%d",
							xmppHost, xmppPort));
					connConfig = new ConnectionConfiguration(xmppHost,
							xmppPort);
				}

				/*ConnectionConfiguration connConfig = new ConnectionConfiguration(
						xmppHost, xmppPort);*/

				// connConfig.setSecurityMode(SecurityMode.disabled);
				connConfig.setSecurityMode(SecurityMode.required);
				connConfig.setSASLAuthenticationEnabled(false);
				connConfig.setCompressionEnabled(false);

				//SmackConfiguration.setKeepAliveInterval(Constants.KEEPAlIVEINTERVAL);
				//SmackConfiguration.setPacketReplyTimeout(Constants.PACKETREPLYTIMEOUT);
				XMPPConnection connection = new XMPPConnection(connConfig);
				xmppManager.setConnection(connection);

				try
				{
					// Connect to the server
					connection.connect();
					TDStockLog.i(TAG, "XMPP connected successfully");

					// packet provider
					ProviderManager.getInstance().addIQProvider(
							"notification", "androidpn:iq:notification",
							new NotificationIQProvider());

				}
				catch (XMPPException e)
				{
					TDStockLog.i(TAG, "XMPP connection failed", e);
					/*
					 * try { Thread.sleep(5000); this.run(); } catch
					 * (InterruptedException e1) { // TODO Auto-generated catch
					 * block e1.printStackTrace(); }
					 */
					//startReconnectionThread();
				}

				// to add this task to futures list
				xmppManager.runTask();
			}
			else
			{
				TDStockLog.i(TAG, "XMPP connected already");
				xmppManager.runTask();
			}

		}
	}

	/**
	 * A runnable task to register a new user onto the server.
	 */
	private class RegisterTask implements Runnable
	{

		final XmppManager	xmppManager;

		private RegisterTask()
		{
			xmppManager = XmppManager.this;
		}

		public void run()
		{
			TDStockLog.i(TAG, "RegisterTask.run()...");
			if (!xmppManager.isRegistered())
			{
				/* 默认是获取随机的名称 */
				// final String newUsername = newRandomUUID();
				/* 修改后用设备ID作为名称* */

				Registration registration = new Registration();

				PacketFilter packetFilter = new AndFilter(
						new PacketIDFilter(registration.getPacketID()),
						new PacketTypeFilter(IQ.class));

				PacketListener packetListener = new PacketListener()
				{

					public void processPacket(Packet packet)
					{
						TDStockLog.d("RegisterTask.PacketListener",
								"processPacket().....");
						TDStockLog.d("RegisterTask.PacketListener",
								"packet=" + packet.toXML());

						if (packet instanceof IQ)
						{
							IQ response = (IQ) packet;
							if (response.getType() == IQ.Type.ERROR)
							{
								if (!response.getError().toString()
										.contains("409"))
								{
									TDStockLog
											.e(
													TAG,
													"Unknown error while registering XMPP account! "
															+ response
																	.getError()
																	.getCondition());
								}
							}
							else if (response.getType() == IQ.Type.RESULT)
							{

								xmppManager.setUsername(newUsername);
								xmppManager.setPassword(newPassword);
								TDStockLog.d(TAG, "username="
										+ newUsername);
								TDStockLog.d(TAG, "password="
										+ newPassword);

								Editor editor = sharedPrefs.edit();
								editor.putString(
										Constants.XMPP_USERNAME,
										newUsername);
								editor.putString(
										Constants.XMPP_PASSWORD,
										newPassword);
								editor.commit();
								TDStockLog
										.i(TAG,
												"Account registered successfully");
								xmppManager.runTask();
							}
						}
					}
				};

				connection.addPacketListener(packetListener, packetFilter);

				registration.setType(IQ.Type.SET);
				// registration.setTo(xmppHost);
				// Map<String, String> attributes = new HashMap<String,
				// String>();
				// attributes.put("username", rUsername);
				// attributes.put("password", rPassword);
				// registration.setAttributes(attributes);
				registration.addAttribute("username", newUsername);
				registration.addAttribute("password", newPassword);
				connection.sendPacket(registration);

			}
			else
			{
				TDStockLog.i(TAG, "Account registered already");
				xmppManager.runTask();
			}
			xmppManager.runTask();
		}
	}

	/**
	 * A runnable task to Log into the server.
	 */
	private class LoginTask implements Runnable
	{

		final XmppManager	xmppManager;

		private LoginTask()
		{
			this.xmppManager = XmppManager.this;
		}

		public void run()
		{
			TDStockLog.i(TAG, "LoginTask.run()...");
			TDStockLog.i(TAG, "isConnection" + xmppManager.isConnected());
			if (!xmppManager.isAuthenticated())
			{
				TDStockLog.d(TAG, "username=" + username);
				TDStockLog.d(TAG, "password=" + password);

				try
				{
					String[] infos = getClientInfo();
					xmppManager.getConnection().login(
							xmppManager.getUsername(),
							xmppManager.getPassword(),
							String.format(XMPP_RESOURCE_NAME, infos[0],
									infos[1]));
					/*
					 * xmppManager.getConnection().login(
					 * xmppManager.getUsername(), xmppManager.getPassword(),
					 * XMPP_RESOURCE_NAME);
					 */
					TDStockLog.d(TAG, "Loggedn in successfully");

					// connection listener
					if (xmppManager.getConnectionListener() != null)
					{
						xmppManager
								.getConnection()
								.addConnectionListener(
										xmppManager
												.getConnectionListener());
					}

					// packet filter
					PacketFilter packetFilter = new PacketTypeFilter(
							NotificationIQ.class);
					// packet listener
					PacketListener packetListener = xmppManager
							.getNotificationPacketListener();
					connection.addPacketListener(packetListener,
							packetFilter);
					//setStatus(ConnectionStatus.CONN);

					//-----------------modified by haibo in 2012-02-15,   no need to tell the port---------------------
					String pushServer = Constants.XMPP_DEFAULT_HOST;
					if (xmppHost != null)
					{
						pushServer = xmppHost;
					}

					NotificationProfile.getInstace().onUpdatePushServer(
							getContext(), pushServer);

					//-----------------modified by haibo in 2012-02-15,   no need to tell the port---------------------

					xmppManager.runTask();

				}
				catch (XMPPException e)
				{
					TDStockLog.e(TAG, "LoginTask.run()... xmpp error");
					TDStockLog.e(TAG,
							"Failed to login to xmpp server. Caused by: "
									+ e.getMessage());
					String INVALID_CREDENTIALS_ERROR_CODE = "401";
					String errorMessage = e.getMessage();
					if (errorMessage != null
							&& errorMessage
									.contains(INVALID_CREDENTIALS_ERROR_CODE))
					{
						xmppManager.reregisterAccount();
						return;
					}
					startReconnectionThread(TAG);
				}
				catch (Exception e)
				{
					TDStockLog.e(TAG, "LoginTask.run()... other error");
					TDStockLog.e(TAG,
							"Failed to login to xmpp server. Caused by: "
									+ e.getMessage());
					startReconnectionThread(TAG);

				}

			}
			else
			{
				TDStockLog.i(TAG, "Logged in already");
				xmppManager.runTask();
			}

		}
	}

	public String[] getClientInfo()
	{
		PackageManager pm = context.getPackageManager();
		PackageInfo pi;
		// Version
		try
		{
			pi = pm.getPackageInfo(context.getPackageName(), 0);
			return new String[] { pi.versionName, android.os.Build.MODEL };
		}
		catch (NameNotFoundException e)
		{
			// TODO Auto-generated catch block
			return Constants.DEFALUT_INFO;
		}
	}

	private int waiting()
	{
		if (waiting > 20)
		{
			return 600;
		}
		if (waiting > 13)
		{
			return 300;
		}
		if (waiting <= 2)
		{
			return 3;
		}
		if (waiting <= 5)
		{
			return 5;
		}
		return waiting <= 7 ? 8 : 30;
	}

}
